﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Axe.Collections;

namespace Axe.Data.Mapping
{

    public class SqlSelect : SqlAction
    {
        object SyncRoot = new object();

        Dictionary<Type, string> SelectingFields = new Dictionary<Type, string>();
        Dictionary<Type, string> SelectAllQuery = new Dictionary<Type, string>();

        public SqlSelect(SqlConnection conn, bool close)
            : base(conn, close)
        {

        }
        public SqlSelect(SqlConnection conn, bool close, SqlTransaction tran, bool commit, bool roolback)
            : base(conn, close, tran, commit, roolback)
        {

        }

        private void BuildSelectQueries(Type type)
        {
            MappingDescription descr = MappingDescriptionBuilder.GetDescription(type);

            Collection<string> fields = new Collection<string>();

            foreach (PropertyDescription pDescr in descr.MappedFields.Values)
            {
                fields.Add(pDescr.MappedName);
            }

            string tableName = descr.Entity.MappedTableName;
            string fieldsText = Utility.Join(fields, ",");

            SelectingFields.Add(type, fieldsText);
            SelectAllQuery.Add(type, String.Format("SELECT {0} FROM [{1}]", fieldsText, tableName));
        }

        internal bool MapperFunction<T>(SqlDataReader reader, T item, MappingDescription descr, string[] mapOnlyThoseFields)
        {
            foreach (string mapField in mapOnlyThoseFields)
            {
                PropertyDescription pDescr = descr.MappedFields[mapField];
                object propertyValue = reader[pDescr.MappedName];
                pDescr.SetValue(item, propertyValue);
            }
            return true;
        }

        internal bool MapperFunction<T>(SqlDataReader reader, T item, MappingDescription descr)
        {
            foreach (PropertyDescription pDescr in descr.MappedFields.Values)
            {
                object propertyValue = reader[pDescr.MappedName];
                pDescr.SetValue(item, propertyValue);
            }

            return true;
        }

        #region public bool SelectAll<T>(ICollection<T> collection)
        /// <summary>
        /// Выбор всех значений из БД
        /// </summary>
        /// <typeparam name="T">Тип выбираемых объектов</typeparam>
        /// <param name="collection">Коллекция, куда будут помещаться объекты</param>
        /// <param name="selectOnlyFields">Набор полей, которые будут выбираться. Может быть пуст. В этом случаем будут извлекаться все доступные поля.</param>
        /// <returns>true, в случае успеха. Иначе - false.</returns>
        public bool SelectAll<T>(ICollection<T> collection, params string[] selectOnlyFields)
        {
            Type typeInstance = typeof(T);
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(typeInstance);

            lock (SyncRoot)
            {
                if (SelectAllQuery.ContainsKey(typeInstance) == false)
                    BuildSelectQueries(typeInstance);
            }

            return Template<bool>(delegate(SqlCommand cmd)
            {
                string cmdFormat = "SELECT {0} FROM [{1}]";
                string mapFieldString = null;

                if (selectOnlyFields != null && selectOnlyFields.Length > 0)
                {
                    mapFieldString = Utility.Join(instDescription.SelectedFields.KeysToValues(selectOnlyFields), ",");
                }
                else
                {
                    mapFieldString = Utility.Join(instDescription.SelectedFields.Values, ",");
                }

                cmd.CommandText = String.Format(cmdFormat,
                    mapFieldString,
                    instDescription.Entity.MappedTableName);

            },
            delegate(SqlCommand cmd)
            {
                bool mapped = SqlMappingPattern.Map<T>(cmd,
                    delegate(SqlDataReader r, T item)
                    {
                        if (selectOnlyFields != null && selectOnlyFields.Length > 0)
                            return MapperFunction(r, item, instDescription, selectOnlyFields);
                        else
                            return MapperFunction(r, item, instDescription);
                    }, collection);

                return mapped;
            });
        }
        #endregion


        #region public T SelectByPK<T>(object pkValue, params string[] selectOnlyFields) where T : class
        public T SelectByPK<T>(object pkValue, params string[] selectOnlyFields) where T : class
        {
            Type objectType = typeof(T);
            return (T)SelectByPK(pkValue, objectType, selectOnlyFields);
        } 
        #endregion

        #region public int SelectRowsCount<T>(Condition c) where T : class
        public int SelectRowsCount<T>(Condition c) where T : class
        {
            Type typeInstance = typeof(T);
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(typeInstance);

            return Template<int>(delegate(SqlCommand cmd)
            {
                string clauseText = RegisterCondition(cmd, c, instDescription);

                cmd.CommandText = String.Format("SELECT COUNT (*) FROM [{0}] WHERE {1}",
                    instDescription.Entity.MappedTableName,
                    clauseText);
            },
            delegate(SqlCommand cmd)
            {
                return Convert.ToInt32(cmd.ExecuteScalar());
            });
        } 
        #endregion

        #region public int SelectRowsCount<T>(CondAggregation cAggr) where T : class
        public int SelectRowsCount<T>(CondAggregation cAggr) where T : class
        {
            Type typeInstance = typeof(T);
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(typeInstance);

            return Template<int>(delegate(SqlCommand cmd)
            {
                string clauseText = RegisterCondition(cmd, cAggr, instDescription);

                cmd.CommandText = String.Format("SELECT COUNT (*) FROM [{0}] WHERE {1}",
                    instDescription.Entity.MappedTableName,
                    clauseText);
            },
            delegate(SqlCommand cmd)
            {
                return Convert.ToInt32(cmd.ExecuteScalar());
            });
        } 
        #endregion

        #region public bool SelectAll<T>(ICollection<T> collection, Condition c, string[] selectOnlyFields) where T : class
        /// <summary>
        /// Выборка данных из БД
        /// </summary>
        /// <typeparam name="T">Сущность</typeparam>
        /// <param name="collection">Коллекция, куда будут складываться объекты</param>
        /// <param name="c">Условие выборки</param>
        /// <param name="selectOnlyFields">Набор выбираемых полей</param>
        /// <returns>Все-ли хорошо</returns>
        public bool SelectAll<T>(ICollection<T> collection, Condition c, params string[] selectOnlyFields) where T : class
        {
            Type typeInstance = typeof(T);
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(typeInstance);
            bool useDefaultSelectSet = selectOnlyFields == null || selectOnlyFields.Length == 0;

            return Template<bool>(
                delegate(SqlCommand cmd)
                {
                    string mapFieldString = null;
                    if(useDefaultSelectSet)
                        mapFieldString = Utility.Join(instDescription.SelectedFields.Values, ",");
                    else
                        mapFieldString = Utility.Join(instDescription.SelectedFields.KeysToValues(selectOnlyFields), ",");

                    string cmdCond = RegisterCondition(cmd, c, instDescription);

                    cmd.CommandText = String.Format("SELECT {0} FROM [{1}] WHERE {2}",
                        mapFieldString,
                        instDescription.Entity.MappedTableName,
                        cmdCond);
                },
                delegate(SqlCommand cmd)
                {
                    bool mapped = SqlMappingPattern.Map<T>(cmd,
                        delegate(SqlDataReader r, T item)
                        {
                            if(useDefaultSelectSet)
                                return MapperFunction(r, item, instDescription);
                            else
                                return MapperFunction(r, item, instDescription, selectOnlyFields);
                        }, collection);
                    return mapped;
                });
        }
        #endregion

        #region public bool SelectAll<T>(ICollection<T> collection, CondAggregation cAggr, params string[] selectOnlyFields)
        public bool SelectAll<T>(ICollection<T> collection, CondAggregation cAggr, params string[] selectOnlyFields)
        {
            Type typeInstance = typeof(T);
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(typeInstance);
            bool useDefaultSelectSet = selectOnlyFields == null || selectOnlyFields.Length == 0;

            return Template<bool>(
                delegate(SqlCommand cmd)
                {
                    string mapFieldString = null;
                    if (useDefaultSelectSet)
                        mapFieldString = Utility.Join(instDescription.SelectedFields.Values, ",");
                    else
                        mapFieldString = Utility.Join(instDescription.SelectedFields.KeysToValues(selectOnlyFields), ",");

                    string cmdCond = RegisterCondition(cmd, cAggr, instDescription);

                    cmd.CommandText = String.Format("SELECT {0} FROM [{1}] WHERE {2}",
                        mapFieldString,
                        instDescription.Entity.MappedTableName,
                        cmdCond);
                },
            delegate(SqlCommand cmd)
            {
                bool mapped = SqlMappingPattern.Map<T>(cmd,
                    delegate(SqlDataReader r, T item)
                    {
                        if (useDefaultSelectSet)
                            return MapperFunction(r, item, instDescription);
                        else
                            return MapperFunction(r, item, instDescription, selectOnlyFields);
                    }, collection);

                return mapped;
            });
        } 
        #endregion

        public object SelectByPK(object ObjectID, Type objectType, params string[] selectOnlyFields)
        {
            MappingDescription instDescription = MappingDescriptionBuilder.GetDescription(objectType);
            if (instDescription.HasPrimaryKey == false)
                throw new InvalidOperationException(Res_Exceptions.MissingPK);

            bool useDefaultSelectSet = selectOnlyFields == null || selectOnlyFields.Length == 0;

            return Template<object>(delegate(SqlCommand cmd)
            {
                string mapFieldString = null;
                if (useDefaultSelectSet)
                    mapFieldString = Utility.Join(instDescription.SelectedFields.Values, ",");
                else
                    mapFieldString = Utility.Join(instDescription.SelectedFields.KeysToValues(selectOnlyFields), ",");

                cmd.CommandText = String.Format("SELECT {0} FROM [{1}] WHERE {2} = @PKVALUE",
                    mapFieldString,
                    instDescription.Entity.MappedTableName,
                    instDescription.PrimaryKey.MappedName);
                cmd.Parameters.AddWithValue("@PKVALUE", ObjectID);

            },

            delegate(SqlCommand cmd)
            {
                object item = Activator.CreateInstance(objectType);
                bool mapped = SqlMappingPattern.MapSingleRowObj(cmd, delegate(SqlDataReader r, object _item)
                {
                    if (useDefaultSelectSet)
                        return MapperFunction(r, _item, instDescription);
                    else
                        return MapperFunction(r, _item, instDescription, selectOnlyFields);
                }, item);

                if (mapped)
                    return item;

                return null;
            });
        }

        public bool HasRows<T>(Condition condition) where T:class
        {
            return SelectRowsCount<T>(condition) > 0;
        }
    }
}
